
    window.onload = async () => {
    const env = {
        serviceUrl: "https://vjmap.com/server/api/v1",
        accessToken: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJJRCI6MiwiVXNlcm5hbWUiOiJhZG1pbjEiLCJOaWNrTmFtZSI6ImFkbWluMSIsIkF1dGhvcml0eUlkIjoiYWRtaW4iLCJCdWZmZXJUaW1lIjo4NjQwMCwiZXhwIjo0ODEzMjY3NjM3LCJpc3MiOiJ2am1hcCIsIm5iZiI6MTY1OTY2NjYzN30.cDXCH2ElTzU2sQU36SNHWoTYTAc4wEkVIXmBAIzWh6M",
        exampleMapId: "sys_zp",
        assetsPath: "../../assets/",
        ...__env__ // 如果您已私有化部署，需要连接已部署的服务器地址和token，请打开js/env.js,修改里面的参数
    };
    try {
        // 在线效果查看地址: https://vjmap.com/map3d/demo/#/demo/map/threejs/210threegamesFps
        // --games_fps--
        // 下面代码参考threejs官方示例改写 https://threejs.org/examples/#games_fps
        let svc = new vjmap3d.Service(env.serviceUrl, env.accessToken);
        let app = new vjmap3d.App(svc, {
            container: "map", // 容器id
            scene: {
                defaultLights: false
            },
            camera: {
                fov: 70,
                near: 0.1,
                far: 1000,
                position: [ 0, 0, 0]
            },
            control: {
                enable: false
            }
        })
        let scene = app.scene, camera = app.camera, renderer = app.renderer;
        
        
        
        const clock = new THREE.Clock();
        
        scene.background = new THREE.Color( 0x88ccee );
        scene.fog = new THREE.Fog( 0x88ccee, 0, 50 );
        
        camera.rotation.order = 'YXZ';
        
        const fillLight1 = new THREE.HemisphereLight( 0x8dc1de, 0x00668d, 1.5 );
        fillLight1.position.set( 2, 1, 1 );
        scene.add( fillLight1 );
        
        const directionalLight = new THREE.DirectionalLight( 0xffffff, 2.5 );
        directionalLight.position.set( - 5, 25, - 1 );
        directionalLight.castShadow = true;
        directionalLight.shadow.camera.near = 0.01;
        directionalLight.shadow.camera.far = 500;
        directionalLight.shadow.camera.right = 30;
        directionalLight.shadow.camera.left = - 30;
        directionalLight.shadow.camera.top	= 30;
        directionalLight.shadow.camera.bottom = - 30;
        directionalLight.shadow.mapSize.width = 1024;
        directionalLight.shadow.mapSize.height = 1024;
        directionalLight.shadow.radius = 4;
        directionalLight.shadow.bias = - 0.00006;
        scene.add( directionalLight );
        
        
        renderer.shadowMap.enabled = true;
        renderer.shadowMap.type = THREE.VSMShadowMap;
        renderer.toneMapping = THREE.ACESFilmicToneMapping;
        
        const GRAVITY = 30;
        
        const NUM_SPHERES = 100;
        const SPHERE_RADIUS = 0.2;
        
        const STEPS_PER_FRAME = 5;
        
        const sphereGeometry = new THREE.IcosahedronGeometry( SPHERE_RADIUS, 5 );
        const sphereMaterial = new THREE.MeshLambertMaterial( { color: 0xdede8d } );
        
        const spheres = [];
        let sphereIdx = 0;
        
        for ( let i = 0; i < NUM_SPHERES; i ++ ) {
        
            const sphere = new THREE.Mesh( sphereGeometry, sphereMaterial );
            sphere.castShadow = true;
            sphere.receiveShadow = true;
        
            scene.add( sphere );
        
            spheres.push( {
                mesh: sphere,
                collider: new THREE.Sphere( new THREE.Vector3( 0, - 100, 0 ), SPHERE_RADIUS ),
                velocity: new THREE.Vector3()
            } );
        
        }
        
        const worldOctree = new Octree();
        
        const playerCollider = new Capsule( new THREE.Vector3( 0, 0.35, 0 ), new THREE.Vector3( 0, 1, 0 ), 0.35 );
        
        const playerVelocity = new THREE.Vector3();
        const playerDirection = new THREE.Vector3();
        
        let playerOnFloor = false;
        let mouseTime = 0;
        
        const keyStates = {};
        
        const vector1 = new THREE.Vector3();
        const vector2 = new THREE.Vector3();
        const vector3 = new THREE.Vector3();
        
        document.addEventListener( 'keydown', ( event ) => {
        
            keyStates[ event.code ] = true;
        
        } );
        
        document.addEventListener( 'keyup', ( event ) => {
        
            keyStates[ event.code ] = false;
        
        } );
        
        document.addEventListener( 'mousedown', () => {
        
            document.body.requestPointerLock();
        
            mouseTime = performance.now();
        
        } );
        
        document.addEventListener( 'mouseup', () => {
        
            if ( document.pointerLockElement !== null ) throwBall();
        
        } );
        
        document.body.addEventListener( 'mousemove', ( event ) => {
        
            if ( document.pointerLockElement === document.body ) {
        
                camera.rotation.y -= event.movementX / 500;
                camera.rotation.x -= event.movementY / 500;
        
            }
        
        } );
        
        app.signal.onAppUpdate.add(animate)
        
        function throwBall() {
        
            const sphere = spheres[ sphereIdx ];
        
            camera.getWorldDirection( playerDirection );
        
            sphere.collider.center.copy( playerCollider.end ).addScaledVector( playerDirection, playerCollider.radius * 1.5 );
        
            // throw the ball with more force if we hold the button longer, and if we move forward
        
            const impulse = 15 + 30 * ( 1 - Math.exp( ( mouseTime - performance.now() ) * 0.001 ) );
        
            sphere.velocity.copy( playerDirection ).multiplyScalar( impulse );
            sphere.velocity.addScaledVector( playerVelocity, 2 );
        
            sphereIdx = ( sphereIdx + 1 ) % spheres.length;
        
        }
        
        function playerCollisions() {
        
            const result = worldOctree.capsuleIntersect( playerCollider );
        
            playerOnFloor = false;
        
            if ( result ) {
        
                playerOnFloor = result.normal.y > 0;
        
                if ( ! playerOnFloor ) {
        
                    playerVelocity.addScaledVector( result.normal, - result.normal.dot( playerVelocity ) );
        
                }
        
                playerCollider.translate( result.normal.multiplyScalar( result.depth ) );
        
            }
        
        }
        
        function updatePlayer( deltaTime ) {
        
            let damping = Math.exp( - 4 * deltaTime ) - 1;
        
            if ( ! playerOnFloor ) {
        
                playerVelocity.y -= GRAVITY * deltaTime;
        
                // small air resistance
                damping *= 0.1;
        
            }
        
            playerVelocity.addScaledVector( playerVelocity, damping );
        
            const deltaPosition = playerVelocity.clone().multiplyScalar( deltaTime );
            playerCollider.translate( deltaPosition );
        
            playerCollisions();
        
            camera.position.copy( playerCollider.end );
        
        }
        
        function playerSphereCollision( sphere ) {
        
            const center = vector1.addVectors( playerCollider.start, playerCollider.end ).multiplyScalar( 0.5 );
        
            const sphere_center = sphere.collider.center;
        
            const r = playerCollider.radius + sphere.collider.radius;
            const r2 = r * r;
        
            // approximation: player = 3 spheres
        
            for ( const point of [ playerCollider.start, playerCollider.end, center ] ) {
        
                const d2 = point.distanceToSquared( sphere_center );
        
                if ( d2 < r2 ) {
        
                    const normal = vector1.subVectors( point, sphere_center ).normalize();
                    const v1 = vector2.copy( normal ).multiplyScalar( normal.dot( playerVelocity ) );
                    const v2 = vector3.copy( normal ).multiplyScalar( normal.dot( sphere.velocity ) );
        
                    playerVelocity.add( v2 ).sub( v1 );
                    sphere.velocity.add( v1 ).sub( v2 );
        
                    const d = ( r - Math.sqrt( d2 ) ) / 2;
                    sphere_center.addScaledVector( normal, - d );
        
                }
        
            }
        
        }
        
        function spheresCollisions() {
        
            for ( let i = 0, length = spheres.length; i < length; i ++ ) {
        
                const s1 = spheres[ i ];
        
                for ( let j = i + 1; j < length; j ++ ) {
        
                    const s2 = spheres[ j ];
        
                    const d2 = s1.collider.center.distanceToSquared( s2.collider.center );
                    const r = s1.collider.radius + s2.collider.radius;
                    const r2 = r * r;
        
                    if ( d2 < r2 ) {
        
                        const normal = vector1.subVectors( s1.collider.center, s2.collider.center ).normalize();
                        const v1 = vector2.copy( normal ).multiplyScalar( normal.dot( s1.velocity ) );
                        const v2 = vector3.copy( normal ).multiplyScalar( normal.dot( s2.velocity ) );
        
                        s1.velocity.add( v2 ).sub( v1 );
                        s2.velocity.add( v1 ).sub( v2 );
        
                        const d = ( r - Math.sqrt( d2 ) ) / 2;
        
                        s1.collider.center.addScaledVector( normal, d );
                        s2.collider.center.addScaledVector( normal, - d );
        
                    }
        
                }
        
            }
        
        }
        
        function updateSpheres( deltaTime ) {
        
            spheres.forEach( sphere => {
        
                sphere.collider.center.addScaledVector( sphere.velocity, deltaTime );
        
                const result = worldOctree.sphereIntersect( sphere.collider );
        
                if ( result ) {
        
                    sphere.velocity.addScaledVector( result.normal, - result.normal.dot( sphere.velocity ) * 1.5 );
                    sphere.collider.center.add( result.normal.multiplyScalar( result.depth ) );
        
                } else {
        
                    sphere.velocity.y -= GRAVITY * deltaTime;
        
                }
        
                const damping = Math.exp( - 1.5 * deltaTime ) - 1;
                sphere.velocity.addScaledVector( sphere.velocity, damping );
        
                playerSphereCollision( sphere );
        
            } );
        
            spheresCollisions();
        
            for ( const sphere of spheres ) {
        
                sphere.mesh.position.copy( sphere.collider.center );
        
            }
        
        }
        
        function getForwardVector() {
        
            camera.getWorldDirection( playerDirection );
            playerDirection.y = 0;
            playerDirection.normalize();
        
            return playerDirection;
        
        }
        
        function getSideVector() {
        
            camera.getWorldDirection( playerDirection );
            playerDirection.y = 0;
            playerDirection.normalize();
            playerDirection.cross( camera.up );
        
            return playerDirection;
        
        }
        
        function controls( deltaTime ) {
        
            // gives a bit of air control
            const speedDelta = deltaTime * ( playerOnFloor ? 25 : 8 );
        
            if ( keyStates[ 'KeyW' ] ) {
        
                playerVelocity.add( getForwardVector().multiplyScalar( speedDelta ) );
        
            }
        
            if ( keyStates[ 'KeyS' ] ) {
        
                playerVelocity.add( getForwardVector().multiplyScalar( - speedDelta ) );
        
            }
        
            if ( keyStates[ 'KeyA' ] ) {
        
                playerVelocity.add( getSideVector().multiplyScalar( - speedDelta ) );
        
            }
        
            if ( keyStates[ 'KeyD' ] ) {
        
                playerVelocity.add( getSideVector().multiplyScalar( speedDelta ) );
        
            }
        
            if ( playerOnFloor ) {
        
                if ( keyStates[ 'Space' ] ) {
        
                    playerVelocity.y = 15;
        
                }
        
            }
        
        }
        let assetsPath = "https://vjmap.com/map3d/resources/three/"
        const loader = vjmap3d.LoadManager.gltfLoader.setPath( assetsPath + './models/gltf/' );
        
        loader.load( 'collision-world.glb', ( gltf ) => {
        
            scene.add( gltf.scene );
        
            worldOctree.fromGraphNode( gltf.scene );
        
            gltf.scene.traverse( child => {
        
                if ( child.isMesh ) {
        
                    child.castShadow = true;
                    child.receiveShadow = true;
        
                    if ( child.material.map ) {
        
                        child.material.map.anisotropy = 4;
        
                    }
        
                }
        
            } );
        
            const helper = new OctreeHelper( worldOctree );
            helper.visible = false;
            scene.add( helper );
        
            const gui = new GUI( { width: 200 } );
            gui.add( { debug: false }, 'debug' )
                .onChange( function ( value ) {
        
                    helper.visible = value;
        
                } );
        
        } );
        
        function teleportPlayerIfOob() {
        
            if ( camera.position.y <= - 25 ) {
        
                playerCollider.start.set( 0, 0.35, 0 );
                playerCollider.end.set( 0, 1, 0 );
                playerCollider.radius = 0.35;
                camera.position.copy( playerCollider.end );
                camera.rotation.set( 0, 0, 0 );
        
            }
        
        }
        
        
        function animate() {
        
            const deltaTime = Math.min( 0.05, clock.getDelta() ) / STEPS_PER_FRAME;
        
            // we look for collisions in substeps to mitigate the risk of
            // an object traversing another too quickly for detection.
        
            for ( let i = 0; i < STEPS_PER_FRAME; i ++ ) {
        
                controls( deltaTime );
        
                updatePlayer( deltaTime );
        
                updateSpheres( deltaTime );
        
                teleportPlayerIfOob();
        
            }
        
        
        }
    }
    catch (e) {
        console.error(e);
    }
};